// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RouteViewModel.cs" company="XamlmNinja">
//   2010
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace BingMaps.ViewModel
{
    using System;
    using System.Collections.Generic;
    using System.Device.Location;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Windows;
    using GalaSoft.MvvmLight.Command;
    using GalaSoft.MvvmLight.Threading;
    using WP7Contrib.Logging;
    using WP7Contrib.Services.BingMaps;
    using WP7Contrib.Services.BingMaps.Model;
    using WP7Contrib.Services.Navigation;
    using WP7Contrib.Services.Storage;

    public class RouteViewModel : ViewModelBaseWp7
    {
        #region Constants and Fields

        private readonly IBingMapsService bingMapsService;

        private GeoCoordinate currentMapSearchCenterPoint;

        private bool isScrolling;

        private RouteSearchResult searchResult;

        private RelayCommand selectCalculateRouteByLandmarksCommand;

        private RelayCommand selectCalculateRouteByPointsCommand;

        private RelayCommand selectCalculateRouteByAddressesCommand;

        private RelayCommand selectCalculateMajorRoadsForLandmarkCommand;

        private IList<ItineraryItem> itineraryItems;

        private Landmark startLandmark;

        private Landmark finishLandmark;

        private string landmarkStatus;

        private GeoCoordinate startPoint;

        private GeoCoordinate finishPoint;

        private string pointStatus;

        private Address startAddress;

        private Address finishAddress;

        private string addressStatus;

        private string majorRoadsStatus;

        private Landmark majorRoads;

        private double zoomLevel;

        #endregion

        #region Constructors and Destructors

        public RouteViewModel(
            INavigationService navigationService, 
            IBingMapsService bingMapsService,
            ILog log)
            : base(navigationService, log)
        {
            this.bingMapsService = bingMapsService;

            this.startLandmark = new Landmark { AreaName = "Big Ben", PostalCode = "SW1A 0AA" } ;
            this.finishLandmark = new Landmark { AreaName = "Stonehenge", PostalCode = "SP4 7DE" };

            this.majorRoads = new Landmark { AreaName = "Space Needle", PostalCode = "WA 98109" };

            this.startPoint = new GeoCoordinate(40.77, -73.98);
            this.finishPoint = new GeoCoordinate(41.51, -87.39);

            this.startAddress = new Address { CountryRegion = "USA", AdminDistrict = "Nevada", PostalCode = "89044" };
            this.finishAddress = new Address { CountryRegion = "USA", AdminDistrict = "California", PostalCode = "90017" };

            DispatcherHelper.CheckBeginInvokeOnUI(() => { this.ZoomLevel = 6; });
        }

        #endregion

        #region Properties

        public GeoCoordinate CurrentMapSearchCenterPoint
        {
            get
            {
                return this.currentMapSearchCenterPoint;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.currentMapSearchCenterPoint, value, "CurrentMapSearchCenterPoint");
            }
        }

        public bool IsScrolling
        {
            get
            {
                return this.isScrolling;
            }

            set
            {
                this.Log.Write("SearchRequestListViewModel: IsScrolling - {0}", value);
                this.isScrolling = value;
            }
        }

        public IList<ItineraryItem> ItineraryItems
        {
            get
            {
                return this.itineraryItems;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.itineraryItems, value, "ItineraryItems");
            }
        }

        public RelayCommand SelectCalculateRouteByLandmarksCommand
        {
            get
            {
                return this.selectCalculateRouteByLandmarksCommand ??
                       (this.selectCalculateRouteByLandmarksCommand =
                        new RelayCommand(this.ExecuteSelectCalculateRouteByLandmarksCommand));
            }
        }

        public RelayCommand SelectCalculateRouteByPointsCommand
        {
            get
            {
                return this.selectCalculateRouteByPointsCommand ??
                       (this.selectCalculateRouteByPointsCommand =
                        new RelayCommand(this.ExecuteSelectCalculateRouteByPointsCommand));
            }
        }

        public RelayCommand SelectCalculateRouteByAddressesCommand
        {
            get
            {
                return this.selectCalculateRouteByAddressesCommand ??
                       (this.selectCalculateRouteByAddressesCommand =
                        new RelayCommand(this.ExecuteSelectCalculateRouteByAddressesCommand));
            }
        }

        public RelayCommand SelectCalculateMajorRoadsForLandmarkCommand
        {
            get
            {
                return this.selectCalculateMajorRoadsForLandmarkCommand ??
                       (this.selectCalculateMajorRoadsForLandmarkCommand =
                        new RelayCommand(this.ExecuteSelectCalculateMajorRoadsForLandmarkCommand));
            }
        }

        public string LandmarkStatus
        {
            get
            {
                return this.landmarkStatus;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.landmarkStatus, value, "LandmarkStatus");
            }
        }

        public Landmark StartLandmark
        {
            get
            {
                return this.startLandmark;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.startLandmark, value, "StartLandmark");
            }
        }

        public Landmark FinishLandmark
        {
            get
            {
                return this.finishLandmark;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.finishLandmark, value, "FinishLandmark");
            }
        }

        public string PointStatus
        {
            get
            {
                return this.pointStatus;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.pointStatus, value, "PointStatus");
            }
        }

        public GeoCoordinate StartPoint
        {
            get
            {
                return this.startPoint;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.startPoint, value, "StartPoint");
            }
        }

        public GeoCoordinate FinishPoint
        {
            get
            {
                return this.finishPoint;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.finishPoint, value, "FinishPoint");
            }
        }

        public string AddressStatus
        {
            get
            {
                return this.addressStatus;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.addressStatus, value, "AddressStatus");
            }
        }

        public Address StartAddress
        {
            get
            {
                return this.startAddress;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.startAddress, value, "StartAddress");
            }
        }

        public Address FinishAddress
        {
            get
            {
                return this.finishAddress;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.finishAddress, value, "FinishAddress");
            }
        }

        public string MajorRoadsStatus
        {
            get
            {
                return this.majorRoadsStatus;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.majorRoadsStatus, value, "MajorRoadsStatus");
            }
        }

        public Landmark MajorRoads
        {
            get
            {
                return this.majorRoads;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.majorRoads, value, "MajorRoads");
            }
        }

        public double ZoomLevel
        {
            get
            {
                return this.zoomLevel;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.zoomLevel, value, "ZoomLevel");
            }
        }

        public RouteSearchResult SearchResult
        {
            get
            {
                return this.searchResult;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.searchResult, value, "SearchResult");
            }
        }

        #endregion

        #region Methods

        protected override void IsBeingActivated(IStorage storage)
        {
        }

        protected override void IsBeingDeactivated(IStorage storage)
        {
        }

        private void ExecuteSelectCalculateRouteByLandmarksCommand()
        {
            try
            {
                DispatcherHelper.CheckBeginInvokeOnUI(
                () =>
                {

                    this.SearchResult = null;
                    this.ItineraryItems = null;
                    this.CurrentMapSearchCenterPoint = null;
                });

                var waypoints = new List<WayPoint> { new WayPoint { Landmark = this.startLandmark }, new WayPoint { Landmark = this.finishLandmark } };
                this.SearchForRouteByLandmarks(CriterionFactory.CreateRouteSearch(waypoints));
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Route Search", MessageBoxButton.OK);
            }
        }

        private void ExecuteSelectCalculateRouteByPointsCommand()
        {
            try
            {
                DispatcherHelper.CheckBeginInvokeOnUI(
                () =>
                {

                    this.SearchResult = null;
                    this.ItineraryItems = null;
                    this.CurrentMapSearchCenterPoint = null;
                });

                var waypoints = new List<WayPoint> { new WayPoint { Point = this.startPoint }, new WayPoint { Point = this.finishPoint } };
                this.SearchForRouteByPoints(CriterionFactory.CreateRouteSearch(waypoints));
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Route Search", MessageBoxButton.OK);
            }
        }

        private void ExecuteSelectCalculateRouteByAddressesCommand()
        {
            try
            {
                DispatcherHelper.CheckBeginInvokeOnUI(
                () =>
                {

                    this.SearchResult = null;
                    this.ItineraryItems = null;
                    this.CurrentMapSearchCenterPoint = null;
                });

                var waypoints = new List<WayPoint> { new WayPoint { Address = this.startAddress }, new WayPoint { Address = this.finishAddress } };
                this.SearchForRouteByAddresses(CriterionFactory.CreateRouteSearch(waypoints));
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Route Search", MessageBoxButton.OK);
            }
        }

        private void ExecuteSelectCalculateMajorRoadsForLandmarkCommand()
        {
            try
            {
                DispatcherHelper.CheckBeginInvokeOnUI(
                () =>
                    {

                        this.SearchResult = null;
                        this.ItineraryItems = null;
                        this.CurrentMapSearchCenterPoint = null;
                    });

                var routeDestination = new RouteDestination { Landmark = this.majorRoads };
                this.SearchForRouteFromMajorRoads(CriterionFactory.CreateRouteSearchFromMajorRoads(routeDestination, DistanceUnit.Kilometer));
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Route Search", MessageBoxButton.OK);
            }
        }

        private static void FailedSearch(Exception exn)
        {
            MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message),
                                          "Route Search",
                                          MessageBoxButton.OK);
        }

        private void ProcessBingRouteSearchResponse(RouteSearchResult result)
        {
            if (result == null)
            {
                return;
            }

            this.SearchResult = result;
            if (this.SearchResult.Points.Count != 0)
            {
                this.CurrentMapSearchCenterPoint = this.SearchResult.Points[0];
                this.ItineraryItems = this.SearchResult.RouteLegs.SelectMany(leg => leg.ItineraryItems).ToList();
            }
        }

        private void ProcessBingRouteFromMajorRoadsSearchResponse(RouteSearchResult result)
        {
            if (result == null)
            {
                return;
            }

            this.SearchResult = result;
            if (result.RouteLegs.Count != 0)
            {
                this.CurrentMapSearchCenterPoint = this.SearchResult.BoundingBox.Center;
                this.ItineraryItems = this.SearchResult.RouteLegs.SelectMany(leg => leg.ItineraryItems).ToList();
            }
        }

        private void SearchForRouteByLandmarks(IRouteSearchCriterion criterion)
        {
            DispatcherHelper.CheckBeginInvokeOnUI(
                () => { this.LandmarkStatus = "Calculating..."; });

            this.bingMapsService.CalculateARoute(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessBingRouteSearchResponse,
                           FailedSearch,
                           () => { this.LandmarkStatus = "Completed, scroll left or right to see Directions & Maps..."; });
        }

        private void SearchForRouteByPoints(IRouteSearchCriterion criterion)
        {
            DispatcherHelper.CheckBeginInvokeOnUI(
                () => { this.PointStatus = "Calculating..."; });

            this.bingMapsService.CalculateARoute(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessBingRouteSearchResponse,
                           FailedSearch,
                           () => { this.PointStatus = "Completed, scroll left or right to see Directions & Maps..."; });
        }

        private void SearchForRouteByAddresses(IRouteSearchCriterion criterion)
        {
            DispatcherHelper.CheckBeginInvokeOnUI(
                () => { this.AddressStatus = "Calculating..."; });

            this.bingMapsService.CalculateARoute(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessBingRouteSearchResponse,
                           FailedSearch,
                           () => { this.AddressStatus = "Completed, scroll left or right to see Directions & Maps..."; });
        }

        private void SearchForRouteFromMajorRoads(IRouteSearchMajorRoadCriterion criterion)
        {
            DispatcherHelper.CheckBeginInvokeOnUI(
              () => { this.MajorRoadsStatus = "Calculating..."; });

            this.bingMapsService.CalculateRoutesFromMajorRoads(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessBingRouteFromMajorRoadsSearchResponse,
                           FailedSearch,
                           () => { this.MajorRoadsStatus = "Completed, scroll left or right to see Directions & Maps..."; });
        }

        #endregion
    }
}